package ch.fusun.baron.data;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;

import ch.fusun.baron.core.injection.ReInjector;
import ch.fusun.baron.core.rmi.KryoUtil;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.ObjectBuffer;

/**
 * Default implementation of the data registry
 */
public class DataRegistryImpl implements DataRegistry, DataListener {

	private static final String DATA_ID = "ch.fusun.baron.data"; //$NON-NLS-1$
	private static final String DATA = "dataProvider"; //$NON-NLS-1$
	private static final String CLASS = "class"; //$NON-NLS-1$
	private static final String DATAUPDATE_ID = "ch.fusun.baron.dataupdate"; //$NON-NLS-1$
	private final Map<Class<?>, DataProvider> data;
	private final List<DataUpdate> changedData = new ArrayList<DataUpdate>();
	private final List<Class<?>> updateClasses = new ArrayList<Class<?>>();

	/**
	 * Constructor
	 */
	public DataRegistryImpl() {
		data = new HashMap<Class<?>, DataProvider>();
		IConfigurationElement[] config = Platform.getExtensionRegistry()
				.getConfigurationElementsFor(DATA_ID);
		try {
			for (IConfigurationElement e : config) {
				DataProvider bean = (DataProvider) e
						.createExecutableExtension(DATA);
				bean = ReInjector.getInstance().getInstanceForClass(
						bean.getClass());
				bean.addDataListener(this);
				data.put(bean.getClass(), bean);
			}
		} catch (CoreException ex) {
			System.err.println("Handle this error with log or so: " //$NON-NLS-1$
					+ ex.getMessage());
		}

		config = Platform.getExtensionRegistry().getConfigurationElementsFor(
				DATAUPDATE_ID);

		for (IConfigurationElement e : config) {
			try {
				DataUpdate bean = (DataUpdate) e
						.createExecutableExtension(CLASS);
				updateClasses.add(bean.getClass());
			} catch (CoreException ex) {
				System.err.println("Handle this error with log or so: " //$NON-NLS-1$
						+ ex.getMessage());
			}
		}

	}

	@Override
	public List<Class<?>> getUpdateClasses() {
		return updateClasses;
	}

	@Override
	public List<DataUpdate> getFullUpdate() {
		List<DataUpdate> updates = new ArrayList<DataUpdate>();
		for (Class<?> clazz : data.keySet()) {
			if (clazz != null) {
				DataProvider dataProvider = data.get(clazz);
				if (dataProvider == null) {
					System.err
							.println("DataProvider for " + clazz + " not found!"); //$NON-NLS-1$//$NON-NLS-2$
				} else {
					updates.add(dataProvider.createFullUpdate());
				}
			}
		}
		return updates;
	}

	@Override
	public List<DataUpdate> getUpdates() {
		return changedData;
	}

	@Override
	public Collection<DataProvider> getDataProviders() {
		return data.values();
	}

	@Override
	public void persist(String path) {
		Kryo kryo = new Kryo();
		ObjectBuffer buffer = new ObjectBuffer(kryo, 100000);
		List<DataProvider> providers = registerClasses(kryo);
		try {
			FileOutputStream fileOutputStream = new FileOutputStream(path);
			buffer.writeObject(fileOutputStream, providers);
			fileOutputStream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private List<DataProvider> registerClasses(Kryo kryo) {
		List<DataProvider> providers = new ArrayList<DataProvider>(
				this.data.values());

		List<Class<?>> clazzes = new ArrayList<Class<?>>();
		for (DataProvider provider : providers) {
			clazzes.add(provider.getClass());
		}

		for (Class<?> clazz : KryoUtil.getInstance()
				.retrieveAllClasses(clazzes)) {
			kryo.register(clazz);
		}
		return providers;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void load(String path) {
		Kryo kryo = new Kryo();
		List<DataProvider> providers = registerClasses(kryo);

		List<DataListener> listeners = new ArrayList<DataListener>();
		for (DataProvider provider : providers) {
			listeners.addAll(provider.getListeners());
			provider.removeAllListeners();
		}

		ObjectBuffer buffer = new ObjectBuffer(kryo, 100000);
		List<DataProvider> loadedProviders = null;
		try {
			FileInputStream fileInputStream = new FileInputStream(path);
			loadedProviders = buffer.readObject(fileInputStream,
					ArrayList.class);
			fileInputStream.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		if (loadedProviders != null) {
			this.data.clear();
			this.changedData.clear();
			for (DataProvider dataProvider : loadedProviders) {
				dataProvider.addDataListener(this);
				data.put(dataProvider.getClass(), dataProvider);
				ReInjector.getInstance().updateInstance(
						dataProvider.getClass(), dataProvider);
			}
			for (DataListener listener : listeners) {
				ReInjector.getInstance().reInject(listener);
				listener.dataChanged(null);
			}
		}
	}

	@Override
	public void dataChanged(DataUpdate update) {
		changedData.add(update);
	}

	@Override
	public void clearUpdates() {
		changedData.clear();
	}
}
